/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2022. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * Description:
 * Author: huawei
 * Create: 2019-10-15
 */

#include "dbl/uda.h"

#include "svm_hot_reset.h"
#include "devmm_channel.h"
#include "svm_msg_client.h"
#include "svm_host_msg_client.h"
#include "svm_device_msg_client.h"
#include "dms_notifier.h"
#include "dms_interface.h"
#include "svm_register_ops.h"
#include "svm_shmem_interprocess.h"
#include "svm_dev_res_mng.h"
#include "devmm_common.h"

STATIC int (*const devmm_callback_dms_notifier_handle_func[DMS_DEVICE_NOTIFIER_MAX]) (u32 dev_id) = {
    [DMS_DEVICE_PRE_HOTRESET] = devmm_hotreset_pre_handle,
    [DMS_DEVICE_HOTRESET_CANCEL] = devmm_hotreset_cancel_handle,
};

STATIC int devmm_callback_dms_notifier_handle(u32 action, void *data)
{
    int dev_id;
    int ret;

    if ((data == NULL) || (action <= DMS_DEVICE_NOTIFIER_MIN) ||
        (action >= DMS_DEVICE_NOTIFIER_MAX)) {
        devmm_drv_err("Invalid parameter. (action=%u; data=\"%s\")\n",
                      action, data == NULL ? "NULL" : "OK");
        return -EINVAL;
    }

    if (devmm_callback_dms_notifier_handle_func[action] == NULL) {
        return 0;
    }

    dev_id = dms_get_devid_from_data(data);
    if (dev_id >= DEVMM_MAX_DEVICE_NUM || dev_id < 0) {
        devmm_drv_err("Invalid parameter. (dev_id=%d)\n", dev_id);
        return -EINVAL;
    }

    ret = devmm_callback_dms_notifier_handle_func[action]((u32)dev_id);
    if (ret != 0) {
        devmm_drv_err("Notifier handle failed. (action=%u; dev_id=%d)\n",
                      action, dev_id);
        return ret;
    }

    return ret;
}

STATIC int devmm_callback_dms_notifier(struct notifier_block *nb, unsigned long action, void *data)
{
    int ret;

    ret = devmm_callback_dms_notifier_handle((u32)action, data);
    if (ret != 0) {
        return NOTIFY_BAD;
    }

    return NOTIFY_DONE;
}

struct notifier_block dms_notifier_devmm = {
    .notifier_call = devmm_callback_dms_notifier,
};

STATIC int devmm_init_instance(u32 devid, struct device *dev)
{
    struct svm_id_inst id_inst;
    int ret;

    ret = devmm_query_smmu_status(dev);
    if (ret != 0) {
        devmm_drv_err("Query smmu_status fail. (dev=%pK)\n", dev);
        return ret;
    }

    svm_id_inst_pack(&id_inst, devid, 0);
    return devmm_dev_res_mng_create(&id_inst, dev);
}

STATIC int devmm_uninit_instance(u32 devid)
{
    struct svm_id_inst id_inst;

    svm_id_inst_pack(&id_inst, devid, 0);

    devmm_svm_business_info_uninit(devid);  /* set stop_business_flag */
    devmm_dev_res_mng_destroy(&id_inst);

    devmm_drv_info("Devmm uninit instance. (devid=%u)\n", devid);
    return 0;
}

#define SVM_HOST_NOTIFIER "svm_host"
static int devmm_host_notifier_func(u32 udevid, enum uda_notified_action action)
{
    int ret = 0;

    if (udevid >= DEVMM_MAX_DEVICE_NUM) {
        devmm_drv_err("Invalid para. (udevid=%u)\n", udevid);
        return -EINVAL;
    }

    if (action == UDA_INIT) {
        ret = devmm_init_instance(udevid, uda_get_device(udevid));
    } else if (action == UDA_UNINIT) {
        ret = devmm_uninit_instance(udevid);
    }

    devmm_drv_info("notifier action. (udevid=%u; action=%d; ret=%d)\n", udevid, action, ret);

    return ret;
}

static int devmm_init_host_agent_res(void)
{
    struct svm_id_inst id_inst;
    int ret;

    svm_id_inst_pack(&id_inst, SVM_HOST_AGENT_ID, 0);
    ret = devmm_dev_res_mng_create(&id_inst, NULL);
    if (ret != 0) {
        devmm_drv_err("Create dev_res_mng for host_agent failed.\n");
    }
    return ret;
}

static void devmm_uninit_host_agent_res(void)
{
    struct svm_id_inst id_inst;

    svm_id_inst_pack(&id_inst, SVM_HOST_AGENT_ID, 0);
    devmm_dev_res_mng_destroy(&id_inst);
}

int devmm_register_ops_init(void)
{
    struct uda_dev_type type;
    int ret;

    /* alloc business mem for hot reset */
    ret = devmm_alloc_business_info();
    if (ret != 0) {
        return ret;
    }

    ret = devmm_init_host_agent_res();
    if (ret != 0) {
        goto init_host_agent_res_fail;
    }

    /* register a callback instance to PCIe, the callback instance include "msg_chan_init hot_reset_handle" */
    devmm_init_msg();
    uda_davinci_near_real_entity_type_pack(&type);
    ret = uda_notifier_register(SVM_HOST_NOTIFIER, &type, UDA_PRI2, devmm_host_notifier_func);
    if (ret != 0) {
        devmm_drv_err("Register devmm_host_instance client error. (ret=%d)\n", ret);
        goto register_client_fail;
    }

    ret = devmm_host_msg_chan_init();
    if (ret != 0) {
        goto msg_chan_init_fail;
    }

    ret = dms_register_notifier(&dms_notifier_devmm);
    if (ret != 0) {
        devmm_drv_err("Dms register devmm notifier failed. (ret=%d)\n", ret);
        goto register_notifier_fail;
    }

    return 0;
register_notifier_fail:
    devmm_host_msg_chan_uninit();
msg_chan_init_fail:
    (void)uda_notifier_unregister(SVM_HOST_NOTIFIER, &type);
register_client_fail:
    devmm_uninit_host_agent_res();
init_host_agent_res_fail:
    devmm_free_business_info();

    return ret;
}

void devmm_unregister_ops_uninit(void)
{
    struct uda_dev_type type;

    /* unregister a callback instance to PCIe, the callback instance include "msg_chan_init hot_reset_handle" */
    devmm_host_msg_chan_uninit();
    uda_davinci_near_real_entity_type_pack(&type);
    (void)uda_notifier_unregister(SVM_HOST_NOTIFIER, &type);

    /* unregister a callback instance to dms, the callback instance include "hot_reset_handle" */
    dms_unregister_notifier(&dms_notifier_devmm);

    devmm_uninit_host_agent_res();

    devmm_free_business_info();
}
